1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.assetmanager;
12 import hip.util.concurrency;
13 import hip.util.data_structures: Node;
14 import hip.util.reflection;
15 import hip.error.handler;
16 import hip.console.log : hiplog;
17 
18 
19 version(WebAssembly) version = CustomRuntime;
20 version(CustomRuntimeTest) version = CustomRuntime;
21 version(PSVita) version = CustomRuntime;
22 
23 private string buildConstantsFromFolderTree(string code, Node!string node, int depth = 0)
24 {
25     import hip.util.path;
26     import hip.util.string;
27     if(node.hasChildren && node.data.extension == "")
28     {
29         code = "\t".repeat(depth)~"class " ~ node.data~ "\n"~"\t".repeat(depth)~"{\n";
30         foreach(child; node.children)
31         {
32             code~= "\t".repeat(depth)~buildConstantsFromFolderTree(code, child, depth+1)~"\n";
33         }
34         code~="\n"~"\t".repeat(depth)~"}\n";
35     }
36     else if(!node.hasChildren && node.data.extension != "")
37     {
38         string propName = node.data[0..$-(node.data.extension.length+1)];
39         return "\tpublic static enum "~propName~" = `"~node.buildPath~"`;";
40     }
41     return code;
42 }
43 
44 mixin template HipAssetsGenerateEnum(string filePath)
45 {
46     import hip.util.path;
47     mixin(buildConstantsFromFolderTree("", buildFolderTree(import(filePath).split('\n'))));
48 }
49 
50 
51 import hip.util.system;
52 import hip.util.concurrency;
53 public import hip.asset;
54 public import hip.assets.image;
55 public import hip.assets.audioclip;
56 public import hip.assets.texture;
57 public import hip.assets.tilemap;
58 public import hip.assets.font;
59 public import hip.assets.csv;
60 public import hip.assets.jsonc;
61 public import hip.assets.ini;
62 public import hip.api.data.commons;
63 public import hip.assets.textureatlas;
64 public import hip.util.data_structures;
65 
66 
67 
68 final class HipAssetLoadTask : IHipAssetLoadTask
69 {
70     string name;
71     string path;
72     HipAssetResult _result = HipAssetResult.cantLoad;
73     HipAsset _asset = null;
74     protected HipWorkerThread worker;
75     protected void[] partialData;
76 
77 
78     private string fileRequesting;
79     private size_t lineRequesting;
80 
81     this(string path, string name, HipAsset asset, string fileRequesting, size_t lineRequesting)
82     {
83         assert(name != null, "Asset load task can't receive null name");
84         this.path = path;
85         this.name = name;
86         this._asset = asset;
87         this.fileRequesting = fileRequesting;
88         this.lineRequesting = lineRequesting;
89         if(asset is null)
90             _result = HipAssetResult.cantLoad;
91         else
92             _result = HipAssetResult.loaded;
93     }
94 
95     bool hasFinishedLoading() const{return result == HipAssetResult.loaded;}
96     bool opCast(T : bool)() const{return hasFinishedLoading;}
97 
98 
99     void addOnCompleteHandler(void delegate(IHipAsset) onComplete)
100     {
101         HipAssetManager.addOnCompleteHandler(this, onComplete);
102     }
103     void addOnCompleteHandler(void delegate(string) onComplete)
104     {
105         HipAssetManager.addOnCompleteHandler(this, (asset)
106         {
107             HipFileAsset theAsset = cast(HipFileAsset)asset;
108             assert(theAsset !is null, "Asset received is not a text");
109             onComplete(theAsset.getText);
110         });
111     }
112 
113 
114     void into(string*[] variables...)
115     {
116         import hip.error.handler;
117         final switch(_result) with(HipAssetResult)
118         {
119             case loaded:
120                 foreach(v; variables)
121                     *v = (cast(HipFileAsset)(asset)).getText;
122                 break;
123             case loading:
124                 //variables are implicitly `scope`, need to duplicate.
125                 string*[] vars = variables.dup;
126                 addOnCompleteHandler((string data)
127                 {
128                     foreach(v; vars)
129                         *v = data;
130                 });
131                 break;
132             case cantLoad:
133                 ErrorHandler.showWarningMessage("Can't load a null asset into a variable address", name);
134                 break;
135         }
136     }
137 
138 
139     void into(void* function(IHipAsset asset) castFunc, IHipAsset*[] variables...)
140     {
141         import hip.error.handler;
142         final switch(_result) with(HipAssetResult)
143         {
144             case loaded:
145                 foreach(v; variables)
146                     *v = cast(IHipAsset)castFunc(asset);
147                 break;
148             case loading:
149                 //variables are implicitly `scope`, need to duplicate.
150                 IHipAsset*[] vars = variables.dup;
151                 addOnCompleteHandler((IHipAsset completeAsset)
152                 {
153                     IHipAsset theAsset = cast(IHipAsset)castFunc(completeAsset);
154                     assert(theAsset !is null, "Null asset received in complete handler?");
155                     foreach(v; vars)
156                         *v = theAsset;
157                 });
158                 break;
159             case cantLoad:
160                 ErrorHandler.showWarningMessage("Can't load a null asset into a variable address", name);
161                 break;
162         }
163     }
164     
165     void await()
166     {
167         if(_result == HipAssetResult.loading)
168             HipAssetManager.awaitTask(this);
169     }
170 
171     void givePartialData(void[] data)
172     {
173         import hip.util.conv:to;
174         if(partialData !is null)
175         {
176             version(CustomRuntime)
177                 assert(false, "AssetLoadTask already has partial data for task "~name~" (requested at "~fileRequesting~":"~lineRequesting.to!string~")");
178             else
179                 throw new Error("AssetLoadTask already has partial data for task "~name~" (requested at "~fileRequesting~":"~lineRequesting.to!string~")");
180         }
181         partialData = data;
182     }
183 
184     void[] takePartialData()
185     {
186         import hip.util.conv:to;
187         if(partialData is null)
188         {
189             version(CustomRuntime)
190                 assert(false, "No partial data was set before taking it for task "~name~ " (requested at "~fileRequesting~":"~lineRequesting.to!string~")");
191             else
192                 throw new Error("No partial data was set before taking it for task "~name~ " (requested at "~fileRequesting~":"~lineRequesting.to!string~")");
193         }
194         void[] ret = partialData;
195         partialData = null;
196         return ret;
197     }
198     
199     HipAssetResult result() const {return _result;}
200     IHipAsset asset(){return _asset;}
201     HipAssetResult result(HipAssetResult newResult){return _result = newResult;}
202     IHipAsset asset(IHipAsset newAsset){return _asset = cast(HipAsset)newAsset;}
203 
204 }
205 
206 
207 
208 import hip.api.data.font;
209 
210 
211 
212 mixin template HipDeferredLoadImpl()
213 {
214     import hip.util.reflection;
215     
216     private void deferredLoad(T, string funcName)(IHipAssetLoadTask task)
217     {
218         alias func = __traits(getMember, typeof(this), funcName);
219         if(task.asset !is null)
220             func( cast(T)task.asset);
221         else
222             HipAssetManager.addOnCompleteHandler(task, (asset)
223             {
224                 func(cast(T)asset);
225             });
226     }
227 
228     pragma(msg, typeof(this).stringof, hasType!"hip.assets.texture.HipTexture",  hasMethod!(typeof(this), "setTexture", IHipTexture));
229     static if(hasType!"hip.assets.texture.HipTexture" && hasMethod!(typeof(this), "setTexture", IHipTexture))
230     {
231         final void setTexture(IHipAssetLoadTask task)
232         {
233             deferredLoad!(HipTexture, "setTexture")(task);
234         }
235     }
236     static if(hasType!"hip.api.data.font.IHipFont" && hasMethod!(typeof(this), "setFont", IHipFont))
237     {
238         final void setFont(IHipAssetLoadTask task)
239         {
240             deferredLoad!(HipFontAsset, "setFont")(task);
241         }
242     }
243 }
244 
245 string HipDeferredLoad()
246 {
247     return q{
248     mixin HipDeferredLoadImpl __dload__;
249     static foreach(func;  __traits(allMembers, __dload__))
250     {
251         mixin("alias ",func," = __dload__.", func,";");
252     }};
253 } 
254 
255 
256 
257 class HipAssetManager
258 {
259     import hip.config.opts;
260 
261     protected __gshared HipWorkerPool workerPool;
262     __gshared float currentTime;
263     //Caching
264     protected __gshared HipAsset[string] assets;
265     protected __gshared HipAssetLoadTask[string] loadQueue;
266 
267     //Thread Communication
268     protected __gshared HipAssetLoadTask[] completeQueue;
269     protected __gshared DebugMutex completeMutex;
270     protected __gshared void delegate(IHipAsset)[][HipAssetLoadTask] completeHandlers;
271     
272 
273 
274     public static void initialize()
275     {
276         completeMutex = new DebugMutex();
277         workerPool = new HipWorkerPool(HIP_ASSETMANAGER_WORKER_POOL);
278     }
279 
280     version(HipConcurrency)
281     {
282         import core.sync.mutex;
283         import std.compiler;
284         static bool isAsync = true;
285     }
286     else
287     {
288         static bool isAsync = false;
289     }
290 
291 
292     @ExportD static IHipAsset getAsset(string name)
293     {
294         if(HipAsset* asset = name in assets)
295             return *asset;
296         return null;
297     }
298 
299     @ExportD static string getStringAsset(string name)
300     {
301         IHipAsset asset = getAsset(name);
302         if(asset !is null)
303         {
304             HipFileAsset fA = cast(HipFileAsset)asset;
305             assert(fA !is null, "Asset fetched is not a file asset.");
306             return fA.getText;
307         }
308         else
309             return null;
310     }
311 
312     static pragma(inline, true) T get(T)(string name) {return cast(T)getAsset(name);}
313     static pragma(inline, true) T get(T : string)(string name) {return getStringAsset(name);}
314 
315     ///Returns whether asset manager is loading anything
316     @ExportD static bool isLoading(){return !workerPool.isIdle;}
317     ///Stops the code from running and awaits asset manager to finish loading
318     @ExportD static void awaitLoad()
319     {
320         workerPool.await;
321         update();
322     }
323 
324     static void awaitTask(HipAssetLoadTask task)
325     {
326         version(HipConcurrency)
327         {
328             import core.sync.semaphore;
329             auto semaphore = new Semaphore(0);
330             task.worker.pushTask("Await Single", ()
331             {
332                 semaphore.notify;
333             });
334             semaphore.wait;
335             destroy(semaphore);
336             update();
337         }
338     }
339 
340     private static HipWorkerThread loadWorker(string taskName, void delegate() loadFn, void delegate(string taskName) onFinish = null, bool onMainThread = false)
341     {
342         //TODO: Make it don't use at all worker and threads.
343         //? Maybe it is not actually needed, as it can be handled by version(HipConcurrency)
344         return workerPool.pushTask(taskName, loadFn, onFinish, onMainThread);
345         // if(isAsync)
346         //     return workerPool.pushTask(taskName, loadFn, onFinish, onMainThread);
347         // else
348         // {
349         //     loadFn();
350         //     if(onFinish !is null)
351         //         onFinish(taskName);
352         // }
353         // return null;
354     }
355 
356     /** 
357      * Checks whether the file has been loaded already or not:
358      *  if: returns its previous task
359      *  else: Put a new one on load cache and retunr
360      */
361     private static HipAssetLoadTask loadBase(string taskName, string path, lazy HipWorkerThread worker, string fileRequesting = __FILE__, size_t lineRequesting = __LINE__)
362     {
363         HipAsset asset = cast(HipAsset)getAsset(path);
364         if(asset !is null){return new HipAssetLoadTask(path, taskName, asset, fileRequesting, lineRequesting);}
365         else if(HipAssetLoadTask* task = path in loadQueue){return *task;}
366 
367         auto task = new HipAssetLoadTask(path, taskName, null, fileRequesting, lineRequesting);
368         loadQueue[path] = task;
369         task.result = HipAssetResult.loading;
370         task.worker = worker;
371         return task;
372     }
373 
374     private static void delegate(HipAsset) onSuccessLoad(HipAssetLoadTask task)
375     {
376         return (HipAsset asset)
377         {
378             ///Will need specific code. Web works differently
379             version(WebAssembly)
380             {
381                 workerPool.signalTaskFinish();
382             }
383             task.asset = asset;
384             task.result = HipAssetResult.loaded;
385             putComplete(task);
386         };
387     }
388 
389     private static void delegate(string err = "") onFailureLoad(HipAssetLoadTask task)
390     {
391         return (err)
392         {
393             ErrorHandler.showWarningMessage("Could not load task: "~ task.name, err);
394             task.result = HipAssetResult.cantLoad;
395             putComplete(task);
396         };
397     }
398 
399 
400     /**
401     *   loadSimple must be used when the asset can be totally constructed on the worker thread and then returned to the main thread
402     */
403     private static HipAssetLoadTask loadSimple(string taskName, string path, void delegate(string pathOrLocation, 
404     void delegate(HipAsset) onSuccess, void delegate(string err = "") onFailure) loadAsset, 
405     string f = __FILE__, size_t l = __LINE__)
406     {
407         HipAssetLoadTask task;
408         taskName = taskName~":"~path;
409         task = loadBase(taskName, path, loadWorker(taskName, ()
410         {
411             loadAsset(path, onSuccessLoad(task), onFailureLoad(task));
412         }), f, l);
413         return task;
414     }
415 
416     version(WebAssembly)
417     {
418         
419         private static void delegate(void[] partialData) onSuccessLoadFirstStep(HipAssetLoadTask task, 
420         void delegate(string taskName) nextStep)
421         {
422             return (void[] partialData)
423             {
424                 task.givePartialData(partialData);
425                 workerPool.notifyOnFinishOnMainThread(nextStep, false)(task.name);
426             };
427         }
428 
429         /**
430         *   The main difference in that version is that it doesn't depends on HipConcurrency to put
431         *   on notifyOnFinish. That was decided because it is impossible to actually know when something
432         *   has finished on Browser. The notfyOnFinish callback must be passed manually.
433         */
434         private static HipAssetLoadTask loadComplex(
435             string taskName,
436             string path,
437             void delegate(
438                 string pathOrLocation, 
439                 void delegate(void[] partialData) onFirstStepComplete, 
440                 void delegate(string err = "") onFailure
441             ) loadAsset, 
442 
443             void delegate (
444                 void[] partialData,
445                 void delegate(HipAsset) onSuccess,
446             ) mainThreadLoadFunction,
447             string f = __FILE__,
448             size_t l = __LINE__
449         )
450         {
451             HipAssetLoadTask task;
452             taskName = taskName~":"~path;
453 
454             auto nextStep = (string _)
455             {
456                 mainThreadLoadFunction(task.takePartialData(), onSuccessLoad(task));
457             };
458 
459             task = loadBase(taskName, path, loadWorker(taskName, ()
460             {
461                 loadAsset(path, onSuccessLoadFirstStep(task, nextStep), onFailureLoad(task));
462             }, null, true), f, l);
463 
464             return task;
465         }
466     }
467     else
468     {
469         /**
470         *   loadComplex is used when part of the asset can be constructed on worker thread, but for completing the load, it must finish on main thread
471         */
472         private static HipAssetLoadTask loadComplex(
473             string taskName,
474             string path,
475             void delegate(
476                 string pathOrLocation, 
477                 void delegate(void[] partialData) onFirstStepComplete, 
478                 void delegate(string err = "") onFailure
479             ) loadAsset, 
480 
481             void delegate (
482                 void[] partialData,
483                 void delegate(HipAsset) onSuccess,
484             ) mainThreadLoadFunction,
485             string f = __FILE__,
486             size_t l = __LINE__
487         )
488         {
489             HipAssetLoadTask task;
490             taskName = taskName~":"~path;
491 
492             task = loadBase(taskName, path, loadWorker(taskName, ()
493             {
494                 loadAsset(path, (void[] partialData)
495                 {
496                     task.givePartialData(partialData);
497                 }, onFailureLoad(task));
498             }, (_)
499             {
500                 mainThreadLoadFunction(task.takePartialData(), onSuccessLoad(task));
501             }, true), f, l);
502 
503             return task;
504         }
505     }
506 
507     @ExportD static IHipAssetLoadTask loadFile(string filePath, string f = __FILE__, size_t l = __LINE__)
508     {
509         void delegate(string,void delegate(HipAsset), void delegate(string err = "")) assetLoadFunc = 
510         (pathOrLocation,onSuccess, onFailure)
511         {
512             import hip.filesystem.hipfs;
513             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
514             {
515                 HipFileAsset asset = new HipFileAsset(pathOrLocation);
516                 asset.load(data);
517                 onSuccess(asset);
518             }).addOnError((string err)
519             {
520                 onFailure("Could not read file with err: " ~ err);
521             });
522         };
523         HipAssetLoadTask task = loadSimple("Load File ", filePath, assetLoadFunc, f, l);
524         workerPool.startWorking();
525         return task;
526     }
527 
528 
529     @ExportD static IHipAssetLoadTask loadImage(string imagePath, string f = __FILE__, size_t l = __LINE__)
530     {
531         void delegate(string,void delegate(HipAsset), void delegate(string err = "")) assetLoadFunc = 
532         (pathOrLocation,onSuccess, onFailure)
533         {
534             import hip.filesystem.hipfs;
535             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
536             {
537                 new Image(pathOrLocation, cast(ubyte[])data, (IImage img){onSuccess(cast(HipAsset)img);}, (){onFailure();});
538             }).addOnError((string err)
539             {
540                 onFailure("Could not read file with err: " ~ err);
541             });
542         };
543         HipAssetLoadTask task = loadSimple("Load Image ", imagePath, assetLoadFunc, f, l);
544         workerPool.startWorking();
545         return task;
546     }
547 
548     /** 
549      * This can be totally loaded on the other thread. loadSimple is enough
550      */
551     @ExportD static IHipAssetLoadTask loadAudio(string audioPath, string f = __FILE__, size_t l = __LINE__)
552     {
553         hiplog("AssetManager: Loading Audio: ", audioPath);
554         void delegate(string, void delegate(HipAsset), void delegate(string err)) assetLoadFunc =
555         (pathOrLocation, onSuccess, onFailure)
556         {
557             import hip.filesystem.hipfs;
558             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
559             {
560                 auto clip = new hip.assets.audioclip.HipAudioClip();
561                 clip.loadFromMemory(data, getEncodingFromName(pathOrLocation), HipAudioType.SFX,
562                 (in ubyte[] newData)
563                 {
564                     hiplog("AssetManager: Audio: Loaded ", audioPath);
565                     onSuccess(clip);
566                 }, (){onFailure("Could not load HipAudioClip.");});
567 
568             }).addOnError((string err)
569             {
570                 onFailure("Could not read file "~audioPath~" with error "~err);
571             });
572         };
573         HipAssetLoadTask task = loadSimple("Load AudioClip", audioPath, assetLoadFunc, f, l);
574         workerPool.startWorking();
575         return task;
576     }
577 
578     @ExportD static IHipAssetLoadTask loadTexture(string texturePath, string f = __FILE__, size_t l = __LINE__)
579     {
580         import hip.util.memory;
581         hiplog("AssetManager: Loading Texture: ", texturePath);
582         void delegate(string, void delegate(void[]), void delegate(string err = "")) assetLoadFunc = 
583         (pathOrLocation, onFirstStepComplete, onFailure)
584         {
585             import hip.filesystem.hipfs;
586             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
587             {
588                 new Image(pathOrLocation, cast(ubyte[])data, 
589                 (IImage img)
590                 {
591                     onFirstStepComplete(toHeapSlice(img));
592                 }, (){onFailure();});
593             }).addOnError((string err)
594             {
595                 ErrorHandler.showErrorMessage("Could not read file ", err);
596             });
597         };
598 
599         void delegate(void[], void delegate(HipAsset)) onPartialDataLoaded = 
600         (partialData, onSuccess)
601         {
602             Image img = cast(Image)(cast(IImage)partialData.ptr);
603             HipTexture ret = new HipTexture(img);
604             hiplog("AssetManager: Texture: Loaded ", texturePath, " ", ret.toHipString);
605             onSuccess(ret);
606             void* gcObjCopy = cast(void*)img;
607             freeGCMemory(gcObjCopy); 
608         };
609         HipAssetLoadTask task = loadComplex("Load Texture", texturePath, assetLoadFunc, onPartialDataLoaded, f, l);
610         workerPool.startWorking();
611         return task;
612     }
613 
614    
615     @ExportD static IHipAssetLoadTask loadCSV(string path, string f = __FILE__, size_t l = __LINE__)
616     {
617         HipAssetLoadTask task = loadSimple("Load CSV", path, (pathOrLocation, onSuccess, onError)
618         {
619             import hip.filesystem.hipfs;
620             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
621             {
622                 auto ret = new HipCSV();
623                 ret.loadFromMemory(cast(string)data);
624                 onSuccess(ret);
625             }).addOnError((string err)
626             {
627                 onError("Error reading file: "~ err);
628             });
629         }, f, l);
630         workerPool.startWorking();
631         return task;
632     }
633     @ExportD static IHipAssetLoadTask loadINI(string path, string f = __FILE__, size_t l = __LINE__)
634     {
635         HipAssetLoadTask task = loadSimple("Load INI", path, (pathOrLocation, onSuccess, onError)
636         {
637             import hip.filesystem.hipfs;
638             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
639             {
640                 auto ret = new HipINI();
641                 ret.loadFromMemory(cast(string)data, pathOrLocation);
642                 onSuccess(ret);
643             }).addOnError((string err)
644             {
645                 onError("Error reading file: "~ err);
646             });
647         }, f, l);
648 
649         workerPool.startWorking();
650         return task;
651     }
652     @ExportD static IHipAssetLoadTask loadJSONC(string path, string f = __FILE__, size_t l = __LINE__)
653     {
654         HipAssetLoadTask task = loadSimple("Load JSONC", path, (pathOrLocation, onSuccess, onError)
655         {
656             import hip.filesystem.hipfs;
657             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
658             {
659                 auto ret = new HipJSONC();
660                 ret.loadFromMemory(cast(string)data);
661                 onSuccess(ret);
662             }).addOnError((string err)
663             {
664                 onError("Error reading file: "~ err);
665             });
666         }, f, l);
667 
668         workerPool.startWorking();
669         return task;
670     }
671 
672     @ExportD static IHipAssetLoadTask loadTextureAtlas(string atlasPath, string texturePath = ":IGNORE", 
673     string f = __FILE__, size_t l = __LINE__)
674     {
675         import hip.util.memory;
676         import hip.assets.textureatlas;
677         class TextureAtlasIntermediaryData
678         {
679             Image image;
680             HipTextureAtlas atlas;
681         }
682         void delegate(string, void delegate(void[]), void delegate(string err = "")) assetLoadFunc = 
683         (pathOrLocation, onFirstStepComplete, onFailure)
684         {
685             import hip.filesystem.hipfs;
686             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
687             {
688                 TextureAtlasIntermediaryData inter = new TextureAtlasIntermediaryData();
689                 inter.atlas = HipTextureAtlas.readFromMemory(cast(ubyte[])data, atlasPath, texturePath);
690                 
691                 HipFS.read(inter.atlas.getTexturePath()).addOnSuccess((in ubyte[] imgData)
692                 {
693                     inter.image = new Image(pathOrLocation, imgData, 
694                     (IImage _)
695                     {
696                         onFirstStepComplete (toHeapSlice(inter));
697                     }, (){onFailure("Failure trying to read image");});
698                 }).addOnError((err){onFailure("Failure trying to read atlas");});
699             }).addOnError((string err)
700             {
701                 ErrorHandler.showErrorMessage("Could not read file: ", err);
702             });
703         };
704 
705         void delegate(void[], void delegate(HipAsset)) onPartialDataLoaded = 
706         (partialData, onSuccess)
707         {
708             scope(exit) freeGCMemory(partialData);
709             auto inter = cast(TextureAtlasIntermediaryData)partialData.ptr;
710             if(!inter.atlas.loadTexture(inter.image))
711             {
712                 assert(false, "Need to implement onError for texture atlas.");
713             }
714             onSuccess(inter.atlas);
715             freeGCMemory(partialData);
716         };
717         HipAssetLoadTask task = loadComplex("Load TextureAtlas", atlasPath, assetLoadFunc, onPartialDataLoaded, f, l);
718 
719         workerPool.startWorking();
720         return task;
721     }
722 
723     @ExportD static IHipAssetLoadTask loadTilemap(string tilemapPath, string f = __FILE__, size_t l = __LINE__)
724     {
725         import hip.util.memory;
726         import hip.assets.tilemap;
727 
728         HipAssetLoadTask task = loadComplex("Load Tilemap ", tilemapPath, (pathOrLocation, onSuccess, onFailure)
729         {
730             import hip.filesystem.hipfs;
731             HipTilemap map;
732             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
733             {
734                 map = HipTilemap.readTiledJSON(pathOrLocation, cast(ubyte[])data, (_)
735                 {
736                     map.loadImages(()
737                     {
738                         onSuccess(toHeapSlice(map));
739                     }, (){onFailure();});
740                 }, (){onFailure();});
741             }).addOnError((string err)
742             {
743                 onFailure("Failed loading tilemap data."~err);
744             });
745             }, (partialData, onSuccess)
746         {
747             scope(exit) freeGCMemory(partialData);
748             auto map = cast(HipTilemap)partialData.ptr;
749             if(!map.loadTextures())
750             {
751                 assert(false, "Could not load HipTilemap textures " ~ map.path);
752             }
753             onSuccess(map);
754         }, f, l);
755         workerPool.startWorking();
756         return task;
757     }
758 
759 
760     @ExportD static IHipAssetLoadTask loadTileset(string tilesetPath, string f = __FILE__, size_t l = __LINE__)
761     {
762         import hip.util.memory;
763         import hip.assets.tilemap;
764         HipAssetLoadTask task = loadComplex("Load Tileset ", tilesetPath, (pathOrLocation, onSuccess, onFailure)
765         {
766             import hip.filesystem.hipfs;
767 
768             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
769             {
770                 auto onTilesetJsonLoaded = delegate(HipTilesetImpl tileset)
771                 {
772                     tileset.loadImage((IImage _)
773                     {
774                         onSuccess(toHeapSlice(tileset));
775                     }, (){onFailure("Failed loading image for tileset");}); 
776                 };
777                 auto onTilesetJsonFailure = delegate(){onFailure("Failed loading tileset json");};
778                 HipTilesetImpl.readFromMemory(pathOrLocation, cast(string)data, onTilesetJsonLoaded, onTilesetJsonFailure);
779             }).addOnError((string err)
780             {
781                 onFailure("Failed reading file for tileset");
782             });
783             }, (partialData, onSuccess)
784         {
785             scope(exit) freeGCMemory(partialData);
786             HipTilesetImpl tileset = cast(HipTilesetImpl)partialData.ptr;
787             if(!tileset.loadTexture())
788                 assert(false, "Could not load HipTileset texture " ~ tileset.path);
789             onSuccess(tileset);
790         }, f, l);
791         workerPool.startWorking();
792         return task;
793     }
794 
795     @ExportD static IHipTextureRegion createTextureRegion(IHipTexture texture, float u1 = 0.0, float v1 = 0.0, float u2 = 1.0, float v2 = 1.0)
796     {
797         return new HipTextureRegion(texture, u1, v1, u2, v2);
798     }
799     @ExportD static IHipTilemap createTilemap(uint width, uint height, uint tileWidth, uint tileHeight)
800     {
801         return new HipTilemap(width, height, tileWidth, tileHeight);
802     }
803     @ExportD static IHipTileset tilesetFromAtlas(IHipTextureAtlas atlas){return HipTilesetImpl.fromAtlas(cast(HipTextureAtlas)atlas);}
804     @ExportD static IHipTileset tilesetFromSpritesheet(Array2D_GC!IHipTextureRegion sp){return HipTilesetImpl.fromSpritesheet(sp);}
805 
806     @ExportD static IHipAssetLoadTask loadFont(string fontPath, int fontSize = 48, string f = __FILE__, size_t l = __LINE__)
807     {
808         import hip.util.path;
809         hiplog("Trying to load the font ", fontPath, "EXT: ", fontPath.extension);
810         switch(fontPath.extension)
811         {
812             case "bmfont":
813             case "fnt":
814                 return loadBMFont(fontPath, f, l);
815             case "ttf":
816             case "otf":
817                 return loadTTF(fontPath, fontSize, f, l);
818             default: return null;
819         }
820     }
821 
822     private static HipAssetLoadTask loadTTF(string ttfPath, int fontSize, string f = __FILE__, size_t l = __LINE__)
823     {
824         import hip.font.ttf;
825         import hip.assets.font;
826         import hip.util.memory;
827 
828         class IntermediaryData
829         {
830             Hip_TTF_Font font;
831             ubyte[] rawImage;
832             this(Hip_TTF_Font fnt, ubyte[] img){font = fnt; rawImage = img;}
833         }
834 
835         HipAssetLoadTask task = loadComplex("Load TTF", ttfPath, (pathOrLocation, onSuccess, onFailure)
836         {
837             import hip.filesystem.hipfs;
838             Hip_TTF_Font font = new Hip_TTF_Font(pathOrLocation, fontSize);
839             ubyte[] rawImage;
840             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data)
841             {
842                 if(!font.partialLoad(cast(ubyte[])data, rawImage))
843                     onFailure("Could not load font data");
844                 onSuccess(toHeapSlice(new IntermediaryData(font, rawImage)));
845             }).addOnError((string err)
846             {
847                 onFailure("Could not read file "~err);
848             });
849         }, (partialData, onSuccess)
850         {
851             scope(exit) freeGCMemory(partialData);
852             IntermediaryData i = (cast(IntermediaryData)partialData.ptr);
853             if(!i.font.loadTexture(i.rawImage))
854             {
855                 assert(false, "Failed loading TTF Font");
856             }
857             HipFontAsset fnt = new HipFontAsset(i.font);
858             onSuccess(fnt);
859         }, f, l);
860         workerPool.startWorking();
861         return task;
862     }
863 
864     private static HipAssetLoadTask loadBMFont(string fontPath, string f = __FILE__, size_t l = __LINE__)
865     {
866         import hip.font.bmfont;
867         import hip.assets.font;
868         import hip.image;
869         import hip.util.memory;
870 
871         class IntermediaryData
872         {
873             HipBitmapFont font;
874             HipImageImpl img;
875             this(HipBitmapFont fnt, HipImageImpl img){font = fnt; this.img = img;}
876         }
877         hiplog("Loading bmfont");
878 
879         HipAssetLoadTask task = loadComplex("Load BMFont", fontPath, 
880         (pathOrLocation, onSuccess, onFailure)
881         {
882             import hip.filesystem.hipfs;
883             HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] fontData)
884             {
885                 HipBitmapFont font = new HipBitmapFont();
886                 if(!font.loadAtlas(cast(string)fontData, pathOrLocation))
887                     return onFailure("Could not load font atlas.");
888                 HipImageImpl img = new HipImageImpl(font.getTexturePath);
889 
890                 HipFS.read(font.getTexturePath).addOnSuccess((in ubyte[] imgData)
891                 {
892                     img.loadFromMemory(cast(ubyte[])imgData, (IImage _)
893                     {
894                         onSuccess(toHeapSlice(new IntermediaryData(font, img)));
895                     }, 
896                     ()
897                     {
898                         onFailure("Could not decode image.");
899                     });
900                 }).addOnError((string err)
901                 {
902                     onFailure("Could not load font image "~err);
903                 });
904                 
905             }).addOnError((string err)
906             {
907                 onFailure("Could read file atlas");
908             });
909         },
910         (partialData, onSuccess)
911         {
912             IntermediaryData i = (cast(IntermediaryData)partialData.ptr);
913             if(!i.font.loadTexture(new HipTexture(i.img)))
914                 assert(false, "Could not read texture");
915             HipFontAsset fnt = new HipFontAsset(i.font);
916             onSuccess(fnt);
917             freeGCMemory(partialData);
918 
919         }, f, l);
920         workerPool.startWorking();
921         return task;
922     }
923     
924 
925    
926     /** 
927      * Synchronized function for putting it into the completed queue for preparing the finish handlers
928      */
929     private static void putComplete(HipAssetLoadTask task)
930     {
931         completeMutex.lock();
932             if(task.result == HipAssetResult.loaded)
933             {
934                 assert((cast(HipAsset)task.asset) !is null, "Can't putComplete a null asset.");
935                 assets[task.path] = cast(HipAsset)task.asset;
936             }
937             completeQueue~= task;
938         completeMutex.unlock();       
939     }
940 
941     static void addOnCompleteHandler(IHipAssetLoadTask task, void delegate(IHipAsset) onComplete)
942     {
943         if(task.asset !is null)
944             onComplete(task.asset);
945         else
946         {
947             hiplog("Added a complete handler for ", (cast(HipAssetLoadTask)task).name);
948             completeHandlers[cast(HipAssetLoadTask)task]~= onComplete;
949         }
950     }
951 
952     static void addOnLoadingFinish(void delegate() onFinish)
953     {
954         workerPool.addOnAllTasksFinished(onFinish);
955     }
956 
957     /**
958     *   This function is responsible for calling worker's onTaskFinish on the main thread if it has one.
959     *   After that, it will execute any deferred task to the AssetManager, such as setting a HipSprite or HipFont asset.
960     */
961     static void update()
962     {
963         completeMutex.lock();
964             if(completeQueue.length)
965             {
966                 foreach(task; completeQueue)
967                 {
968                     //Subject to a logger
969                     hiplog(task.name, " executing handlers");
970                     if(auto handlers = task in completeHandlers)
971                     {
972                         foreach(handler; *handlers)
973                             handler(task._asset);
974                         handlers.length = 0;
975                     }
976                     completeHandlers.remove(task);
977                 }
978                 completeQueue.length = 0;
979             }
980         completeMutex.unlock();
981         workerPool.pollFinished();
982     }
983 
984     /**
985     *   Cleans everything up. Puts AssetManager in an invalid state
986     */
987     static void dispose()
988     {
989         import hip.error.handler;
990         workerPool.dispose();
991         foreach(HipAsset asset; assets.byValue)
992             asset.dispose();
993         destroy(assets);
994         destroy(loadQueue);
995         destroy(completeMutex);
996         destroy(workerPool);
997     }
998 }